use std::{ffi::OsStr, path::Path};

use rayon::prelude::*;
use walkdir::WalkDir;

static PRESET_HEADER: &str = r#"
#import "/contrib/templates/std-tests/preset.typ": *
#show: test-page
"#;

fn main() -> anyhow::Result<()> {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let test_dir = &manifest_dir.join("../../../typst/tests/typ");
    let rewrite_dir = manifest_dir.join("../../fuzzers/corpora");
    let std_artifact_path = manifest_dir.join("../../tests/common/src/std_artifact.rs");

    let mut test_files = WalkDir::new(test_dir)
        .into_iter()
        .par_bridge()
        .filter_map(|entry| {
            let entry = entry.unwrap();
            if entry.depth() == 0 {
                return None;
            }

            // if entry.path().starts_with("typ/benches") {
            //     return None;
            // }

            let src_path = entry.into_path();
            if src_path.extension() != Some(OsStr::new("typ")) {
                return None;
            }

            // if args.matches(&src_path) {
            //     Some(src_path)
            // } else {
            //     None
            // }
            Some(src_path)
        })
        .flat_map(|src_path| -> anyhow::Result<Vec<(String, String)>> {
            // eprintln!("src_path={}", src_path.display());

            // read source
            let text = std::fs::read_to_string(&src_path)?;

            let mut parts: Vec<_> = text
                .split("\n---")
                .map(|s| s.strip_suffix('\r').unwrap_or(s))
                .collect();

            let has_header = !parts.is_empty()
                && parts[0]
                    .lines()
                    .all(|s| s.starts_with("//") || s.chars().all(|c| c.is_whitespace()));

            if has_header {
                parts.remove(0);
            }

            let pref = src_path.strip_prefix(test_dir)?;
            if pref.components().collect::<Vec<_>>().len() < 2 {
                return Ok(vec![]);
            }
            let cat = pref
                .components()
                .next()
                .unwrap()
                .as_os_str()
                .to_string_lossy()
                .to_string();

            // we don't care compiler and compute tests
            if cat == "compiler" || cat == "compute" {
                return Ok(vec![]);
            }

            // write to rewrite_dir
            let rewrite_path = rewrite_dir.join(pref);
            std::fs::create_dir_all(rewrite_path.parent().unwrap())?;

            let mut rewrite_paths = vec![];
            for (i, part) in parts.iter().enumerate() {
                let file_name = rewrite_path.file_stem().unwrap().to_string_lossy();
                let file_name = format!("{file_name}_{i:02}");
                eprintln!("scanned {cat:10} {file_name}...");

                let rewrite_path = rewrite_path.with_file_name(format!("{file_name}.typ"));

                rewrite_paths.push((cat.clone(), file_name));

                // replace /files by /assets/files
                let mut part = part
                    .replace("\"../../files/", "\"/assets/files/")
                    .replace("\"/files/", "\"/assets/files/");

                if part.contains("// Error:") {
                    part = part
                        .lines()
                        .map(|s| "// ".to_owned() + s)
                        .collect::<Vec<_>>()
                        .join("\n");
                }

                std::fs::write(&rewrite_path, PRESET_HEADER.to_owned() + &part)?;
            }

            Ok(rewrite_paths)
        })
        .flatten()
        .collect::<Vec<_>>();

    test_files.sort();
    let test_files = test_files
        .iter()
        .map(|s| format!("{s:?}"))
        .collect::<Vec<_>>()
        .join(",\n    ");

    std::fs::write(
        std_artifact_path,
        format!(
            r#"// This file is generated by `cargo run -p std --bin typst-ts-std-test`
#![cfg_attr(rustfmt, rustfmt_skip)]

pub const STD_TEST_FILES: &[(&str, &str)] = &[
    {test_files}
];
"#
        ),
    )?;

    Ok(())
}
